Option Explicit

' 大写字符 A 的 ascii 编码码值
Public Const UCASE_CHAR_CODE_A = 65
' 大写字符 Z 的 ascii 编码码值
Public Const UCASE_CHAR_CODE_Z = 90
' 小写字符 a 的 ascii 编码码值
Public Const LCASE_CHAR_CODE_A = 97
' 小写字符 z 的 ascii 编码码值
Public Const LCASE_CHAR_CODE_Z = 122
' 数字 0 的 ascii 编码码值
Public Const NUM_CHAR_CODE_0 = 48
' 数字 9 的 ascii 编码码值
Public Const NUM_CHAR_CODE_9 = 57

'去除字符串右边的指定字符
'@param src
'@param trimStr
Public Function trimRight(ByRef src As String, ByRef trimStr As String) As String
    Dim notEatSrcLen As Integer
    Dim srcLen As Integer
    srcLen = Len(src)
    notEatSrcLen = srcLen

    Dim trimLen As Integer
    trimLen = Len(trimStr)

    Do While (notEatSrcLen > 0)
        If Mid(src, notEatSrcLen - trimLen + 1, trimLen) = trimStr Then
            notEatSrcLen = notEatSrcLen - trimLen
        Else
            Exit Do
        End If
    Loop

    If notEatSrcLen = srcLen Then
        trimRight = src
    ElseIf notEatSrcLen = 0 Then
        trimRight = ""
    Else
        trimRight = Mid(src, 1, notEatSrcLen)
    End If
End Function

'去除字符串左边的指定字符
'@param src
'@param trimStr
Public Function trimLeft(ByRef src As String, ByRef trimStr As String) As String
    Dim ateSrcLen As Integer
    Dim srcLen As Integer
    srcLen = Len(src)
    ateSrcLen = 0

    Dim trimLen As Integer
    trimLen = Len(trimStr)

    Do While (ateSrcLen < srcLen)
        If Mid(src, ateSrcLen + 1, trimLen) = trimStr Then
            ateSrcLen = ateSrcLen + trimLen
        Else
            Exit Do
        End If
    Loop

    If ateSrcLen = srcLen Then
        trimLeft = ""
    ElseIf ateSrcLen = 0 Then
        trimLeft = src
    Else
        trimLeft = Mid(src, ateSrcLen + 1)
    End If
End Function

' 去除字符串左边和右边的指定字符串。如果左边或者右边有重复的字符串，将会全部删除
'@param src
'@param trimStr
Public Function trimBy(ByRef src As String, ByRef trimStr As String) As String
    Dim srcLen As Integer
    srcLen = Len(src)

    Dim trimLen As Integer
    trimLen = Len(trimStr)

    ' 1. 处理左边的字符
    Dim ateSrcLeftLen As Integer
    ateSrcLeftLen = 0
    Do While (ateSrcLeftLen < srcLen)
        If Mid(src, ateSrcLeftLen + 1, trimLen) = trimStr Then
            ateSrcLeftLen = ateSrcLeftLen + trimLen
        Else
            Exit Do
        End If
    Loop

    Dim startIndex As Integer
    If ateSrcLeftLen = srcLen Then
        trimBy = ""
        Exit Function
    ' 无论 ateSrcLeftLen 是否 = 0， + 1 都正确
    Else
        startIndex = ateSrcLeftLen + 1
    End If

    ' 2. 处理右边的字符
    Dim notEatSrcLen As Integer
    notEatSrcLen = srcLen - ateSrcLeftLen

    Do While (notEatSrcLen > 0)
        If Mid(src, ateSrcLeftLen + notEatSrcLen - trimLen + 1, trimLen) = trimStr Then
            notEatSrcLen = notEatSrcLen - trimLen
        Else
            Exit Do
        End If
    Loop

    If notEatSrcLen = srcLen Then
        trimBy = src
    ElseIf notEatSrcLen = 0 Then
        trimBy = ""
    Else
        trimBy = Mid(src, startIndex, notEatSrcLen)
    End If
End Function

' 检查字符串是否以指定字符串开头
' @param src 待检查的字符串
' @param prefix 前缀字符串
' @return Boolean
Public Function startsWith(ByRef src As String, ByRef prefix As String) As Boolean
    Dim prefixLen as String
    prefixLen = Len(prefix)
    If prefixLen = 0 Then
        startsWith = True
    ElseIf prefixLen > Len(src) Then
        startsWith = False
    Else
        If Left(src, prefixLen) = prefix Then
            startsWith = True
        Else
            startsWith = False
        End If
    End If
End Function

' 检查字符串是否以指定字符串结尾
' @param src 待检查的字符串
' @param suffix 后缀字符串
' @return Boolean
Public Function endsWith(ByRef src As String, ByRef suffix As String) As Boolean
    Dim suffixLen As Integer
    suffixLen = Len(suffix)
    If suffixLen = 0 Then
        endsWith = True
    ElseIf suffixLen > Len(src) Then
        endsWith = False
    Else
        If Right(src, suffixLen) = suffix Then
            endsWith = True
        Else
            endsWith = False
        End If
    End If
End Function

' 检查字符串是否全是数字
' @param text 待检查的字符串
' @return Boolean
Public Function isNumStr(ByRef text As String) As Boolean
    If text = "" Then
        isNumStr = False
    Else

        Dim c As String
        Dim i As Integer

        isNumStr = True
        For i = 1 To Len(text)
            c = Mid(text, i, 1)
            If Asc(c) < 48 Or Asc(c) > 57 Then
                isNumStr = False
                Exit For
            End If
        Next

    End If
End Function

' 去除字符串中的所有数字
' @param text 待检查的字符串
' @return String
Public Function removeAllNum(ByRef text As String) As String
    Dim c As String
    Dim i As Integer

    removeAllNum = ""
    For i = 1 To Len(text)
        c = Mid(text, i, 1)
        If Asc(c) < 48 Or Asc(c) > 57 Then
            removeAllNum = removeAllNum & c
        End If
    Next
End Function

' 去除字符串中的指定字符
' @param text 待检查的字符串
' @param targetChar 需要去除的字符
' @return String
Public Function removeChar(ByRef text As String, ByRef targetChar As String) As String
    Dim c As String
    Dim i As Integer

    removeChar = ""
    For i = 1 To Len(text)
        c = Mid(text, i, 1)
        If c <> targetChar Then
            removeChar = removeChar & c
        End If
    Next
End Function

' 从字符串中抽取所有的数字字符
' @param text 待检查的字符串
' @return String
Public Function extractNum(ByRef text As String) As String
    Dim c As String
    Dim i As Integer

    extractNum = ""
    For i = 1 To Len(text)
        c = Mid(text, i, 1)
        If Asc(c) >= 48 And Asc(c) <= 57 Then
            extractNum = extractNum & c
        End If
    Next
End Function

' 只保留字符串中的数字 + 大小写英文字母
' @param text 待检查的字符串
' @return String
Public Function extractNumAndEn(ByRef text As String) As String
    Dim c As String
    Dim cCode As Integer
    Dim i As Integer

    extractNumAndEn = ""
    For i = 1 To Len(text)
        c = Mid(text, i, 1)
        cCode = Asc(c)
        If (cCode >= NUM_CHAR_CODE_0 And cCode <= NUM_CHAR_CODE_9) Or _
            (cCode >= UCASE_CHAR_CODE_A And cCode <= UCASE_CHAR_CODE_Z) Or _
            (cCode >= LCASE_CHAR_CODE_A And cCode <= LCASE_CHAR_CODE_Z) Then
            extractNumAndEn = extractNumAndEn & c
        End If
    Next
End Function

' 检查字符串中的所有字符是不是大小写英文字母
' @param src 待检查的字符串
' @return String
Public Function isEnStr(ByRef src As String) As Boolean
    Dim cCode As Integer
    Dim i As Integer
    isEnStr = True
    For i = 1 To Len(src)
        cCode = Asc(Mid(src, i, 1))
        If Not ((cCode >= UCASE_CHAR_CODE_A And cCode <= UCASE_CHAR_CODE_Z) Or _
            (cCode >= LCASE_CHAR_CODE_A And cCode <= LCASE_CHAR_CODE_Z)) Then
            isEnStr = False
            Exit For
        End If
    Next
End Function

' 检查数字的长度，如果小于指定长度，则在左侧补0
' @param num 待检查的数字
' @param length 期待的数字长度
' @return String
Public Function addLeftZeroForNum(ByRef num As Integer, ByRef length As Integer) As String
    addLeftZeroForNum = num & ""

    If Len(addLeftZeroForNum) < length Then
        Dim i As Integer

        For i = 1 To length - Len(addLeftZeroForNum)
            addLeftZeroForNum = "0" & addLeftZeroForNum
        Next
    End If
End Function

' 重复字符串
' @param text 待重复的字符串
' @param repeatCount 重复次数
' @return String
Public Function repeatStr(ByRef text As String, ByRef repeatCount As Long) As String
    repeatStr = ""

    Dim i As Integer
    For i = 1 To repeatCount
        repeatStr = repeatStr & text
    Next
End Function

' 去除 [lv ~ rv]字符范围内的所有字符
' @param src 待检查的字符串
' @param lv 左侧范围的字符
' @param rv 右范围的字符
' @example
'   a = "123456789012345678"
'   b = removeByRangeTag(a, "1", "5") ' 删除 1～5之间的所有字符
'   Debug.print b = "67890678"
Public Function removeByRangeTag(ByRef src As String, ByRef lv As String, ByRef rv As String) As String
    Dim lvIdx As Integer
    Dim rvIdx As Integer
    Dim rvLen As Integer
    Dim leftStr As String
    Dim rightStr As String
    Dim srcLen As String

    rvLen = Len(rv)

    removeByRangeTag = src
    Do While True
        lvIdx = InStr(removeByRangeTag, lv)
        If lvIdx = 0 Then
            Exit Do
        End If

        rvIdx = InStr(removeByRangeTag, rv)

        If lvIdx > rvIdx Then
            Exit Do
        End If

        If lvIdx = 1 Then
            leftStr = ""
        Else
            leftStr = Mid(removeByRangeTag, 1, lvIdx - 1)
        End If

        srcLen = Len(removeByRangeTag)
        If rvIdx = srcLen Then
            rightStr = ""
        Else
            rightStr = Mid(removeByRangeTag, rvIdx + rvLen, srcLen - rvIdx + rvLen)
        End If

        removeByRangeTag = leftStr & rightStr
    Loop
End Function

' 判断(第一个)字符是不是大写字母
' @param src 字符串
' @return Boolean
Public Function firstIsUpperCase(ByRef src As String) As Boolean
    Dim firstCharCode As Integer
    firstCharCode = Asc(Mid(src, 1, 1))

    If firstCharCode >= UCASE_CHAR_CODE_A And firstCharCode <= UCASE_CHAR_CODE_Z Then
        firstIsUpperCase = True
    Else
        firstIsUpperCase = False
    End If
End Function

' 判断(第一个)字符是小写字母
' @param src 字符串
' @return Boolean
Public Function firstIsLowerCase(ByRef src As String) As Boolean
    Dim firstCharCode As Integer
    firstCharCode = Asc(Mid(src, 1, 1))

    If firstCharCode >= LCASE_CHAR_CODE_A And firstCharCode <= LCASE_CHAR_CODE_Z Then
        firstIsLowerCase = True
    Else
        firstIsLowerCase = False
    End If
End Function

' 驼峰命名形式的字符串转蛇形命名形式的字符串
' @param src 待变换的字符串
' @return String
' @example
'    Debug.Print UtilsString.Camel2Snake("AaCccc") = "aa_cccc"
'
'    Debug.Print UtilsString.Camel2Snake("aaCccc") = "aa_cccc"
'
'    Debug.Print UtilsString.Camel2Snake("aacccc") = "aacccc"
'
'    Debug.Print UtilsString.Camel2Snake("aaC") = "aa_c"
'
'    Debug.Print UtilsString.Camel2Snake("Aa") = "aa"
'
'    Debug.Print UtilsString.Camel2Snake("aa") = "aa"
'
'    Debug.Print UtilsString.Camel2Snake("a") = "a"
'
'    Debug.Print UtilsString.Camel2Snake("A") = "a"
Public Function camel2Snake(ByRef src As String) As String

    Dim c As String
    Dim prevIsUCase As Boolean
    Dim i As Integer

    camel2Snake = LCase(Mid(src, 1, 1))
    prevIsUCase = True

    For i = 2 To Len(src)
        c = Mid(src, i, 1)
        If firstIsUpperCase(c) Then
            If prevIsUCase Then
                camel2Snake = camel2Snake & LCase(c)
            Else
                camel2Snake = camel2Snake & "_" & LCase(c)
            End If

            prevIsUCase = True
        Else
            camel2Snake = camel2Snake & c
            prevIsUCase = False
        End If
    Next
End Function

' 分割字符串，并保留分隔符
' @param expression 待变换的字符串
' @param delimiter 分隔符
' @return String()
' @example
'   splitHoldDelimiter("A1A2A3", "A") ---> ("A1", "A2", "A3")
Public Function splitHoldDelimiter(ByRef expression As String, ByRef delimiter As String) As String()
    Dim i As Integer
    Dim delimiterLen As Integer
    delimiterLen = Len(delimiter)

    ' get Count of delimiter
    Dim delimiterCount As Integer
    delimiterCount = 0
    For i = 1 To Len(expression)
        If Mid(expression, i, delimiterLen) = delimiter Then
            delimiterCount = delimiterCount + 1
            i = i + delimiterLen
        End If
    Next

    ' delimiter = X, expression = 111X1X2
    ' delimiterCount = 2, should be 3
    If expression <> "" And Mid(expression, 1, delimiterLen) <> delimiter Then
        delimiterCount = delimiterCount + 1
    End If

    ' split
    Dim result() As String
    ReDim result(0 To delimiterCount - 1) As String

    Dim curStr As String
    curStr = ""

    Dim splitorIdx As Integer
    splitorIdx = 0

    For i = 1 To Len(expression)
        If Mid(expression, i, delimiterLen) = delimiter Then
            If curStr <> "" Then
                result(splitorIdx) = curStr
                splitorIdx = splitorIdx + 1
            End If

            curStr = delimiter
            i = i + delimiterLen - 1
        Else
            curStr = curStr & Mid(expression, i, 1)
        End If
    Next
    result(splitorIdx) = curStr

    ' return
    splitHoldDelimiter = result
End Function

' 按照正则表达式替换所有匹配到的字符
' @param text 待处理的字符串
' @param regStr 正则表达式
' @param replaceStr 用来替换的字符串
' @return String
Public Function regReplaceAll(ByRef text As String, ByRef regStr As String, ByRef replaceStr As String) As String
    Dim regexp As Object: Set regexp = UtilsCreateSysObj.createRegexp()

    regexp.Global = True
    regexp.Pattern = regStr

    regReplaceAll = regexp.Replace(text, replaceStr)

    Set regexp = Nothing
End Function

' 按照正则表达式匹配，并删除所有匹配到的字符
' @param text 数据源
' @param regStr 正则表达式
' @return String
Public Function removeAllByReg(ByRef text As String, ByRef regStr As String) As String
    removeAllByReg = UtilsString.regReplaceAll(text, regStr, "")
End Function

' 比较字符串右侧，如果相同，则进行替换
' @param text 待检查的字符串
' @param excepted 需要检查的右侧字符串
' @param updated 比较成功时，用于替换的字符串
' @return String
'    text = "12345abcd"
'    excepted = "abcd"
'    updated = "qwer"
'    result = compareRightAndUpdate(text, excepted, updated)
'    Debug.Print result = "12345qwer"
Public Function compareRightAndUpdate(ByRef text As String, ByRef excepted As String, ByRef updated As String) As String
    Dim exceptedLen As Integer
    exceptedLen = Len(excepted)

    If Len(text) < exceptedLen Then
        compareRightAndUpdate = text
        Exit Function
    End If

    If Right(text, exceptedLen) = excepted Then
        compareRightAndUpdate = Left(text, Len(text) - exceptedLen) & updated
    Else
        compareRightAndUpdate = text
    End If

End Function

' 比较字符串左侧，如果相同，则进行替换
' @param text 待检查的字符串
' @param excepted 需要检查的左侧字符串
' @param updated 比较成功时，用于替换的字符串
' @return String
'    text = "12345abcd"
'    excepted = "abcd"
'    updated = "qwer"
'    result = compareRightAndUpdate(text, excepted, updated)
'    Debug.Print result = "12345qwer"
Public Function compareLeftAndUpdate(ByRef text As String, ByRef excepted As String, ByRef updated As String) As String
    Dim exceptedLen As Integer
    exceptedLen = Len(excepted)

    If Len(text) < exceptedLen Then
        compareLeftAndUpdate = text
        Exit Function
    End If

    If Left(text, exceptedLen) = excepted Then
        compareLeftAndUpdate = updated & Right(text, Len(text) - exceptedLen)
    Else
        compareLeftAndUpdate = text
    End If

End Function

' 去除字符串末尾的换行符，包括 vbCrLf, vbLf
' @param text 待处理的字符串
' @return String 替换后的字符串
Public Function deleteStrEndLineBreaks(ByRef text As String) As String
    ' 1. 字符串的可用长度
    Dim useableLen As Integer
    useableLen = Len(text)

    Dim temp As String

    ' 2. 遍历检查末尾字符串，并获取可用长度
    While useableLen > 0
        ' 2.1 如果字符串可用长度大于 2，则检查: 末尾是否是 vbCrLf
        If useableLen >= 2 Then
            If Mid(text, useableLen - 1, 2) = vbCrLf Then
                ' 字符串可用长度 - 2
                useableLen = useableLen - 2

                ' 进入下一次遍历
                Goto while1Continue
            End If
        End If

        ' 2.2 检查: 末尾是否是 vbLf 或者 vbCr
        temp = Mid(text, useableLen, 1)
        If temp = vbLf Then
            ' 字符串可用长度 - 1
            useableLen = useableLen - 1

            ' 进入下一次遍历
            Goto while1Continue
        ElseIf temp = vbCr Then
            ' 字符串可用长度 - 1
            useableLen = useableLen - 1

            ' 进入下一次遍历
            Goto while1Continue
        Else
            ' 2.3 否则，退出遍历
            Goto while1Break
        End If
while1Continue:
    Wend
while1Break:

    ' 3. 截取字符串
    deleteStrEndLineBreaks = Left(text, useableLen)
End Function

' 从 `text` 中截取 `[startIdx, endIdx)` 的字符串
' @param text 参照字符串
' @param startIdx 起始索引
' @param endIdx 结束索引
' @param String 截取后的字符串
Public Function substring(ByRef text As String, ByVal startIdx As Integer, Optional ByVal endIdx As Integer = -1) As String
    Dim textLen As Integer
    textLen = Len(text)

    If startIdx < 0 Then
        ' 如果 `startIdx < 0`，则抛出异常，异常码 `10010`
        Err.Raise 10010, Description:="parameter[startIdx]=" & startIdx & " out of bounds. min index=0, max index[" & (Len(text) - 1) & "]"
    ElseIf startIdx > textLen Then
        ' `startIdx > text的长度`，则默认返回空字符串 `""`
        substring = ""
        Exit Function
    End If

    If endIdx < 0 Then
        ' 如果 `endIdx < 0`，则默认使用 `text的长度`
        endIdx = textLen
    ElseIf endIdx > textLen Then
        ' 如果 `endIdx > text的长度`，将会使用 `text的长度` 作为 `endIdx`
        endIdx = textLen
    End If

    ' 如果 `startIdx >= endIdx`，则默认返回空字符串 `""`
    If startIdx >= endIdx Then
        substring = ""
        Exit Function
    End If

    ' 计算需要截取的长度，并截取字符串
    Dim resultSize As Integer
    resultSize = endIdx - startIdx
    If resultSize = textLen Then
        ' 如果截取的长度和 text 长度相同，则直接返回 text
        substring = text
    Else
        substring = Mid(text, startIdx + 1, resultSize)
    End If
End Function

' 生成一定数量的左括号
' @param repeatCount 重复次数
' @return String
Public Function repeatLeftParenthesis(ByVal repeatCount As Long) As String
    repeatLeftParenthesis = repeatStr("(", repeatCount)
End Function

' 生成一定数量的右括号
' @param repeatCount 重复次数
' @return String
Public Function repeatRightParenthesis(ByVal repeatCount As Long) As String
    repeatRightParenthesis = repeatStr(")", repeatCount)
End Function

' 搜索第一个不是指定目标字符 `target` 的字符在 `str` 中的索引
' 索引从 `1` 开始
'
' @param str 原始字符串
' @param target 需要检查的目标字符串
Public Function findFirstNoTargetIndex(ByRef str As String, ByRef target As String) As Integer
    ' 1. 如果 `str` 是空字符串，则返回 `0`
    If str = "" Then
        ' return 0
        Exit Function
    End If

    Dim targetLen As Integer
    targetLen = Len(target)

    ' 2. 从 i = 1 开始到 Len(str) 遍历 str 的每一个字符
    Dim i As Integer
    For i = 1 To (Len(str) - targetLen + 1)
        ' 发现了一个不是 target 的字符串后，保存这个索引，并退出遍历
        If Mid(str, i, targetLen) <> target Then
            findFirstNoTargetIndex = i
            Exit For
        End If
    Next

    ' 3. 退出处理
    '    如果已经发现了一个不是 target 的字符，则在循环里已经保存过索引了，所以这里可以直接返回
    '    如果 str 中的所有字符都是 target，则会正常退出遍历，findFirstNoTargetIndex 仍然是 0，可以正常退出
End Function


' 使用换行符来分割字符串。分割时自动适应 CR(旧 mac 换行符) 和 CRLF(windows换行符) 和 LF 换行符
'
' @param txt 待分割的字符串
' @return String(), 分割后的字符串数组
Public Function splitByAnyLineBreak(ByRef txt As String) As String()
    Dim workTxt As String

    ' CRLF --> LF
    workTxt = Replace(txt, vbCrLf, vbLf)
    ' CR --> LF
    workTxt = Replace(workTxt, vbCr, vbLf)

    splitByAnyLineBreak = Split(workTxt, vbLf)
End Function

' 分割字符串
' 当分割符的长度为 1 时，如果分隔符前面有奇数个转义字符 `\`，则不会将这计数个转义字符和分隔符做分割
'
' @param txt 待分割的字符串
' @param delimiter 分隔符
' @param String() 分割后的字符串数组
Public Function splitHoldEscape(ByRef txt As String, ByRef delimiter As String) As String()
    Dim parts() As String
    ReDim parts(0)
    Dim currentPart As String
    Dim i As Long

    For i = 1 To Len(txt)
        Dim c As String
        c = Mid(txt, i, 1)

        If c = "/" Then
            If IsEscaped(txt, i) Then
                currentPart = currentPart & c
            Else
                ' 分割字符串
                ReDim Preserve parts(UBound(parts) + 1)
                parts(UBound(parts) - 1) = currentPart
                currentPart = ""
            End If
        Else
            currentPart = currentPart & c
        End If
    Next i

    ' 添加最后一个部分
    ReDim Preserve parts(UBound(parts) + 1)
    parts(UBound(parts)) = currentPart

    splitHoldEscape = parts
End Function

Private Function IsEscaped(s As String, pos As Long) As Boolean
    Dim count As Long
    count = 0
    Dim i As Long
    For i = pos - 1 To 1 Step -1
        If Mid(s, i, 1) = "\" Then
            count = count + 1
        Else
            Exit For
        End If
    Next i
    IsEscaped = (count Mod 2 = 1) ' 奇数个反斜杠表示被转义
End Function

' 检查给定的字符串是否是有同一个字符组成的
'
' @param v 需要检查的字符串
' @return 给定的字符串是否是有同一个字符组成的
Public Function isAllSameChar(ByVal v As String) As Boolean
    ' 1. 检查，如果是空字符串或者只有一个字符串，则默认返回 True
    Dim strLen As Integer
    strLen = Len(v)
    If strLen = 0 Or strLen = 1 Then
        isAllSameChar = True
        Exit Function
    End If

    ' 2. 获取字符串的第一个字符
    Dim temp As String
    temp = Left(v, 1)

    ' 3. 尝试将所有字符替换为空
    temp = Replace(v, temp, "")

    ' 4. 如果替换后的字符串长度为 0，则说明这个字符串是由同一个字符组成的
    isAllSameChar = Len(temp) = 0
End Function

' 检查给定的字符串是否全部由指定的字符组成
'
' @param v 需要检查的字符串
' @param specialChar 需要检查的目标字符
' @return 给定的字符串是否全部由指定的字符组成
Public Function isAllSameSpecialChar(ByVal v As String, ByRef specialChar As String) As Boolean
    ' 1. 检查，如果是空字符串，则默认返回 False
    If Len(v) = 0 Then
        isAllSameSpecialChar = False
        Exit Function
    End If

    ' 2. 尝试将指定字符替换为空，如果替换后的字符串长度为 0，则说明这个字符串是由同一个字符组成的
    isAllSameSpecialChar = Len(Replace(v, specialChar, "")) = 0
End Function

' 在 `src` 中搜索 `targetChar`，并在其前面添加转义字符 `\`，如果 `targetChar` 前面已经有转义字符 `\` 了，将不会进行替换
'
' @param src 原始字符串
' @param targetChar 需要附加转义字符的目标字符
' @return 转换后的字符串
Public Function escapeTargetStr( _
    ByRef src As String, _
    ByRef targetChar As String _
) As String
    ' 1. 获取目标字符的长度
    Dim targetCharLen As Integer
    targetCharLen = Len(targetChar)

    ' 2. 搜索第一个元素的位置
    Dim i As Integer
    i = InStr(1, src, targetChar)

    ' 3. 循环搜索，直到全部替换完成
    Dim lastMark As Integer
    lastMark = 1
    Do While i > 0
        ' 3.1. 附加目标字符串前面的字符串
        escapeTargetStr = escapeTargetStr & Mid(src, lastMark, i - lastMark)

        ' 3.2 如果目标字符前面不是转义字符，则附加
        If Right(escapeTargetStr, 1) <> "\" Then
            escapeTargetStr = escapeTargetStr & "\" & targetChar
        Else
        ' 3.3 否则，直接添加目标字符
            escapeTargetStr = escapeTargetStr & targetChar
        End If

        i = i + targetCharLen
        lastMark = i

        ' 3.3 继续搜索剩余的字符串
        i = InStr(i, src, targetChar)
    Loop

    ' 4. 附加剩余的字符串
    escapeTargetStr = escapeTargetStr & Right(src, Len(src) - lastMark + 1)
End Function